package org.artifact.core.db;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Function;

import cn.hutool.core.thread.NamedThreadFactory;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
@SuppressWarnings("unchecked")
public abstract class BaseDao<T extends BaseBean<T>>{
	
	static final Log log = LogFactory.get(BaseDao.class);
	
	// 待入库集合
	protected Map<Object, T> cachedUpdates = new ConcurrentHashMap<>();
	
	protected Map<Object, T> cachedDeletes = new ConcurrentHashMap<>();
	
	// 非守护线程，避免存储数据不完整
	protected ExecutorService saveThreadPool = Executors.newSingleThreadExecutor(new NamedThreadFactory("Save-Thread-" + this.getClass().getSimpleName(), false));
	
//	{
//		if (!existsTable()) {
//			createTable();
//		}
//	}
	
	/** 返回列表 */
	protected List<T> resultList(Callable<List<T>> callable, Function<T, Boolean> fun) {
		// 过滤数据
		Future<List<T>> future = saveThreadPool.submit(() -> {
			List<T> result = callable.call();
			
			List<T> lists = new ArrayList<>();

			for (T t : cachedUpdates.values()) {
				if (fun.apply(t)) {
					lists.add(t);
				}
			}

			for (T t : result) {
				if (cachedDeletes.containsKey(t.pk())) {
					continue;
				}
				if (cachedUpdates.containsKey(t.pk())) {
					continue;
				}
				lists.add(t);
			}
			return lists;
		});
		
		try {
			return future.get(3000, TimeUnit.MILLISECONDS);
		} catch (InterruptedException | ExecutionException | TimeoutException e) {
			log.error(e);
			return new ArrayList<>();
		}
	}

	/** 返回单个 */
	protected T resultFirst(Callable<T> callable, Function<T, Boolean> fun) {
		// 过滤数据
		Future<T> future = saveThreadPool.submit(() -> {
			T result = null;
			for (T bean : cachedUpdates.values()) {
				if (fun.apply(bean)) {
					result = bean;
					break;
				}
			}
			if (result==null) {
				result = callable.call();
			}

			if (result != null) {
				if (cachedDeletes.containsKey(result.pk())) {
					return null;
				}
			}

			return result;
		});

		try {
			return future.get(3000, TimeUnit.MILLISECONDS);
		} catch (InterruptedException | ExecutionException | TimeoutException e) {
			log.error(e);
			return null;
		}
	}
	
	/** 单个保存 */
	public void save(T t) {
		final T save = (T) t.toSave();
		saveThreadPool.execute(() -> cachedUpdates.put(t.pk(), save));
	}

	/** 单个删除 */
	public void remove(T t) {
		final T save = (T) t.toSave();
		saveThreadPool.execute(() -> cachedDeletes.put(t.pk(), save));
	}
	
	/** 变参保存 */
	public void save(T...t) {
		final Object[] saves = new Object[t.length];
		for (int i = 0; i < t.length; i++) {
			saves[i] = t[i].toSave();
		}
		saveThreadPool.execute(() -> {
			for (Object obj : saves) {
				T save = (T) obj;
				cachedUpdates.put(save.pk(), save);
			}
		});
	}

	/** 变参删除 */
	public void remove(T...t) {
		final Object[] saves = new Object[t.length];
		for (int i = 0; i < t.length; i++) {
			saves[i] = t[i].toSave();
		}
		saveThreadPool.execute(() -> {
			for (Object obj : saves) {
				T save = (T) obj;
				cachedDeletes.put(save.pk(), save);
			}
		});
	}
	
	/** 批量保存 */
	public void save(Collection<T> coll) {
		final Object[] saves = new Object[coll.size()];
		Iterator<T> iterator = coll.iterator();
		int i = 0;
		while (iterator.hasNext()) {
			saves[i] = iterator.next().toSave();
			i++;
		}
		saveThreadPool.execute(() -> {
			for (Object obj : saves) {
				T save = (T) obj;
				cachedUpdates.put(save.pk(), save);
			}
		});
	}
	
	/** 批量删除 */
	public void remove(Collection<T> coll) {
		final Object[] saves = new Object[coll.size()];
		Iterator<T> iterator = coll.iterator();
		int i = 0;
		while (iterator.hasNext()) {
			saves[i] = iterator.next().toSave();
			i++;
		}
		
		saveThreadPool.execute(() -> {
			for (Object obj : saves) {
				T save = (T) obj;
				cachedDeletes.put(save.pk(), save);
			}
		});
	}
	
	/** 停止存储线程 */
	public void shutdown() {
		// 默认3分钟超时
		int timeout = 180000;
		saveThreadPool.shutdown();
		try {
			if (!saveThreadPool.awaitTermination(timeout, TimeUnit.MILLISECONDS)) {
				log.error("-> SaveThreadPool Shutdown TimeOut Table:{}", this.getClass().getSimpleName());
				saveThreadPool.shutdownNow();
			} else {
				log.info("-> SaveThreadPool Shutdown Success Table:{}", this.getClass().getSimpleName());
			}
		} catch (InterruptedException ie) {
			log.error("-> SaveThreadPool Shutdown Error Table:{} {} : ", this.getClass().getSimpleName(), ie);
			saveThreadPool.shutdownNow();
			Thread.currentThread().interrupt();
		}
		landingDatabase();
	}
	
	/** 刷入数据库 */
	public void finishAll() {
		saveThreadPool.execute(() -> {
			landingDatabase();
		});
	}

	private void landingDatabase() {
		// 1.存储
		if (!cachedUpdates.isEmpty()) {
			finishSaveCache();	
			cachedUpdates.clear();
		}
		// 2.删除
		if (!cachedDeletes.isEmpty()) {
			finishDeleteCache();	
			cachedDeletes.clear();
		}
	}
	
	/** 把待保存的数据-刷入数据库 */
	protected abstract void finishSaveCache();
	
	/** 把待删除的数据-刷入数据库 */
	protected abstract void finishDeleteCache();
	
	/** 取得数据库名称 */
	protected abstract String getDBName();
	
	/** 取得表名称 */
	protected abstract String getTableName();
	
	/** 取得对应的-EntityClass */
	protected abstract Class<T> getEntityClass();
	
	/** 检查表是否存在 */
	protected abstract T createEntity();
	
	/** 检查表是否存在 */
	public abstract boolean existsTable();
	
	/** 创建表 */
	public abstract boolean createTable();
	
	/** 查询全部 */
	public abstract List<T> findAll();
	
	/** 根据主键查询 */
	public abstract T findByPrimaryKey(Object primaryKey);
	
	/** 取得主键字段名称,数据库使用必须是源字段名 */
	protected abstract String getPKFieldName();
}
